package com.joysuch.wwyt.datasync.service.impl;

import com.joysuch.wwyt.datasync.DataSyncType;
import com.joysuch.wwyt.datasync.annotation.DataSync;
import com.joysuch.wwyt.datasync.service.DataSyncCondition;
import com.joysuch.wwyt.datasync.support.AopDataSyncDaoInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;

import java.lang.reflect.Field;

public class BaseDataSyncCondition implements DataSyncCondition {

    @Override
    public AopDataSyncDaoInterceptor.Info extractSyncInfo(MethodInvocation invocation) {
        if(invocation.getMethod().getName().startsWith("find")
                || invocation.getMethod().getName().startsWith("select")
                || invocation.getMethod().getName().startsWith("count")
                || invocation.getMethod().getName().startsWith("get")
        ) {
            return null;
        }
        AopDataSyncDaoInterceptor.Info info = parseBasicInfo(invocation);
        return info;
    }

    protected AopDataSyncDaoInterceptor.Info parseBasicInfo(MethodInvocation invocation) {
        DataSync sync = invocation.getMethod().getAnnotation(DataSync.class);
        if (sync != null) {
            return new AopDataSyncDaoInterceptor.Info(invocation, sync, getDomainClass(invocation));
        }

        String methodName = invocation.getMethod().getName();
        if (methodName.startsWith("save")) {
            JpaMetamodelEntityInformation domainClass = getDomainClass(invocation);
            Object[] args = invocation.getArguments();
            if (args.length == 0) {
                return null;
            }
            DataSyncType type = getTypeFromArgs(domainClass, args);
            if (type == null) {
                return null;
            }
            return new AopDataSyncDaoInterceptor.Info(invocation, domainClass, type);
        } else if (methodName.startsWith("delete")) {
            return new AopDataSyncDaoInterceptor.Info(invocation, getDomainClass(invocation), DataSyncType.DELETE);
        }

        return null;
    }

    JpaMetamodelEntityInformation getDomainClass(MethodInvocation invocation) {
        SimpleJpaRepository repo = (SimpleJpaRepository) invocation.getThis();
        JpaMetamodelEntityInformation info = (JpaMetamodelEntityInformation) getValue(repo, "entityInformation");
        return info;
    }

    public static Object getValue(Object obj, String props) {
        try {
            Field f = obj.getClass().getDeclaredField(props);
            f.setAccessible(true);
            return f.get(obj);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }


    private DataSyncType getTypeFromArgs(JpaMetamodelEntityInformation domainClass, Object[] args) {
        for (Object arg : args) {
            if (arg instanceof Iterable) {
                Iterable it = (Iterable) arg;
                for (Object obj : it) {
                    DataSyncType type = getTypeById(domainClass, obj);
                    if (type != null) {
                        return type;
                    }
                }
            } else {
                DataSyncType type = getTypeById(domainClass, arg);
                if (type != null) {
                    return type;
                }
            }
        }
        return null;
    }

    DataSyncType getTypeById(JpaMetamodelEntityInformation domainClass, Object arg) {
        if (domainClass.getJavaType().isAssignableFrom(arg.getClass())) {
            Object id = domainClass.getId(arg);
            if (id == null) {
                // 没有ID属性
                return DataSyncType.ADD;
            }
            return DataSyncType.UPDATE;
        }
        return null;
    }
}
